#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.Configuration;
using System.Runtime.CompilerServices;
using XcoAppSpaces.Contracts.Transport;
using XcoAppSpaces.Contracts.Exceptions;
using XcoAppSpaces.Contracts.Logging;

namespace XcoAppSpaces.Transport.WCF
{
	/// <summary>
	/// Sender for communication with remote spaces. Uses an IRemoteSpaceService to communicate
	/// with service hosts of remote spaces.
	/// </summary>
    public class RemoteSender
	{
		//private static readonly ILogger log = LoggerFactory.CreateInstance("XcoSpaces.Services.Kernel.Communication", System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private ChannelFactory<SpaceServiceChannel> cf = null;
        private SpaceServiceChannel spaceService;
	    private IXcoWCFTransportSessionShutdown shutdown = null;
		private DateTime lastSendTime = DateTime.Now;
		private bool connectionOK = true;
		private IXcoLogger log = null;
        private IBindingsHelper bindingHelper = new BindingsHelper();
	    private ServiceModelSectionGroup smsGroup; // configuration from external source
	    private string _sessionID = null;

		private readonly string address;
        
		internal void SetBindingHelper(IBindingsHelper bindingHelper)
        {
            this.bindingHelper = bindingHelper;
        }
       
        private ChannelFactory<SpaceServiceChannel> LoadConfiguration(string address)
        {
            bool wcfConfigurationExists = false;
            ServiceModelSectionGroup group;
            if (smsGroup == null)
            {
                //load configuration from the standard-exe-config section
                Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);



                //load the model section in the config
                 group = ServiceModelSectionGroup.GetSectionGroup(config);
            }
            else
            {// external configuration exists
                group = smsGroup;
                
            }
            string standardConfig = "XcoClient";
                
                //search for the wcf-standard xco-client-section in configuration
                foreach (ChannelEndpointElement endpoint in group.Client.Endpoints)
                {
                    if (endpoint.Name == standardConfig)
                    {//found one endpoint with the standard config
                        wcfConfigurationExists = true;
                        break;
                    }
                }
          

            if (!wcfConfigurationExists)
            {

                //log.Debug("RemoteSender() - No wcf-config found - use default settings");
                //log.Debug("No WCF-Configuration <" + standardConfig + "> found - using default");
                NetTcpBinding maxBufferTCP = new NetTcpBinding();   //NetTcpBinding with large buffer sizes
                maxBufferTCP.MaxReceivedMessageSize = 512000;
                maxBufferTCP.MaxBufferSize = 512000;
                maxBufferTCP.ReaderQuotas.MaxArrayLength = 512000;
                maxBufferTCP.Security.Mode = SecurityMode.None;     // no security by default

                //create channel factory with default settings
                return new ChannelFactory<SpaceServiceChannel>(maxBufferTCP);

            }
            else
            {
                //log.Debug("RemoteSender() - WCF Config.");
                //create channel factory with program settings
                return new ChannelFactory<SpaceServiceChannel>(standardConfig);

            }
        }
        
        /// <summary>
        /// Supports different sender-configurations
        /// </summary>
        /// <returns></returns>
        public delegate ChannelFactory<SpaceServiceChannel> LoadConfigurationDelegate(string address);


		/// <summary>
		/// Creates a new RemoteSender. A new IRemoteSpaceService object is created that communicates
		/// with the given address, with help of the ChannelFactory class.
		/// </summary>
		/// <param name="address">The address of the remote space, in binding form.</param>
		/// <param name="log">Logger to use for logging.</param>
        /// <param name="group">External configuration for WCF settings</param>
        /// <param name="shutdown">Shutdown event for faster cleanup</param>
        public RemoteSender(string address, IXcoLogger log, ServiceModelSectionGroup group, IXcoWCFTransportSessionShutdown shutdown)            
        {
			this.address = address;
            this.shutdown = shutdown;
			this.log = log;
		    this.smsGroup  = group;
            Init(LoadConfiguration);
        }

        /// <summary>
        /// Creates a new RemoteSender. A new IRemoteSpaceService object is created that communicates
        /// with the given address, with help of the ChannelFactory class.
        /// </summary>
        /// <param name="address">The address of the remote space, in binding form.</param>
		/// <param name="loadConfig">Delegate for loading the service configuration.</param>
		/// <param name="log">The logger that should be used.</param>
        /// <param name="shutdown">Shutdown event for faster cleanup</param>
		/// <param name="bindingHelper">The binding helper that should be used</param>
		public RemoteSender(string address, LoadConfigurationDelegate loadConfig, IXcoLogger log, IXcoWCFTransportSessionShutdown shutdown, IBindingsHelper bindingHelper)
        {
			this.address = address;
			this.shutdown = shutdown;
            this.log = log;
			this.bindingHelper = bindingHelper;
			Init(loadConfig);
        }

        private void Init(LoadConfigurationDelegate loadConfig)
        {
            //XVSMClientRelayEndpoint
            cf = loadConfig(address);
            if (cf == null)
            {
                cf = LoadConfiguration(address);
            }

            if (cf.Endpoint.Address == null)
            {
                cf.Endpoint.Address = new EndpointAddress(bindingHelper.Convert(cf.Endpoint.Binding, address));
            }

            cf.Endpoint.Behaviors.Add(new XcoWCFEndpointBehavior(shutdown)); // get shutdown event...
            spaceService = cf.CreateChannel();

            log.Debug("Creating new RemoteSender for RemoteAddress " + address);
        }

        /// <summary>
        /// Connection lost
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void spaceService_Closed(object sender, EventArgs e)
        {
            if (shutdown!=null)
            {
                shutdown.Shutdown(SessionID);
            }
        }


	    /// <summary>
		/// The session id of the client channel that is used by communicating with the remote space service.
		/// </summary>
		public string SessionID
		{
			get
			{
                if (string.IsNullOrEmpty(_sessionID))
                {
                    _sessionID = ((IClientChannel) spaceService).SessionId;
                }
                return _sessionID;
			}
		}
		
		/// <summary>
		/// The time when this RemoteSender has last sent a message.
		/// </summary>
		public DateTime LastSendTime
		{
			get { return lastSendTime; }
		}

		/// <summary>
		/// Initiates a connection by calling the Connect method of the IRemoteSpaceService object.
		/// </summary>
		public void Connect()
		{
			spaceService.Connect();						
		}

		/// <summary>
		/// Test - After channel shutdown, sending is not allowed
		/// </summary>
		public void SendDisable()
		{
			connectionOK = false;
		}

		private bool disconnected = false;
		/// <summary>
		/// Terminates the connection by calling the Disconnect method of the IRemoteSpaceService object.
		/// </summary>
		public void Disconnect()
		{
			connectionOK = false;
			if (disconnected)
				return;
			disconnected = true;
			log.Info("Closing Remotesender... (" + address + ")"); 
			try
            {//terminate connection 
                if (spaceService.State == CommunicationState.Opened)
                {
                    spaceService.Disconnect();
                }
            }
            catch (Exception err)
            {
                log.Warn("Error disconnecting from spaceService: " + err.ToString());
            }		

			try
			{
                if (cf != null)
                {
					cf.Close(); //close endpoint connection
                }
			}
			catch (Exception err)
			{
                log.Warn("Error closing ChannelFactory: " + err.ToString());
			}
			log.Info("Remotesender closed (was connected to: " + address + ")");
		}

		/// <summary>
		/// Sends a message by first serializing it and the using the IRemoteSpaceService object to send it.
		/// The last send time is set to the current time.
		/// </summary>
		/// <param name="msg">The Message to be sent.</param>
		/// <param name="remoteAddress">The address where the message should sent.</param>
		public void SendMessage(XcoMessage msg, string remoteAddress)
		{
			if (connectionOK && cf.State == CommunicationState.Opened)
            {
				try
				{
					//Console.WriteLine("Sending Message: " + msg);
					spaceService.SendMessage(msg, remoteAddress);
					lastSendTime = DateTime.Now;
				}
				catch (Exception err)
				{
					connectionOK = false;
					log.Error("Error - SendMessage ", err);
					throw new XcoException("Error - SendMessage ", err);
				}
            }
            else
            {
				connectionOK = false;
                throw new XcoException("Connection not opened");
            }
		}
	}
}
